#include <iostream>

#include <gtest/gtest.h>

#include <amo/json_object.hpp>
#include <amo/type_traits.hpp>
#include <memory>
#include <vector>
#include <list>

#include <amo/timer.hpp>







class JsonInfoPod : public amo::json_object {
public:
    AMO_ENTITY_ARGS_GET_BEGIN(JsonInfoPod)
    AMO_ENTITY_ARGS_GET(m_bool)
    AMO_ENTITY_ARGS_GET(m_char)
    AMO_ENTITY_ARGS_GET(m_int8_t)
    AMO_ENTITY_ARGS_GET(m_int16_t)
    AMO_ENTITY_ARGS_GET(m_int32_t)
    AMO_ENTITY_ARGS_GET(m_int64_t)
    AMO_ENTITY_ARGS_GET(m_uint8_t)
    AMO_ENTITY_ARGS_GET(m_uint16_t)
    AMO_ENTITY_ARGS_GET(m_uint32_t)
    AMO_ENTITY_ARGS_GET(m_uin64_t)
    AMO_ENTITY_ARGS_GET(m_float)
    AMO_ENTITY_ARGS_GET(m_double)
    AMO_ENTITY_ARGS_GET(m_string)
    AMO_ENTITY_ARGS_GET_END()
    
    AMO_ENTITY_ARGS_SET_BEGIN(JsonInfoPod)
    AMO_ENTITY_ARGS_SET(m_bool)
    AMO_ENTITY_ARGS_SET(m_char)
    AMO_ENTITY_ARGS_SET(m_int8_t)
    AMO_ENTITY_ARGS_SET(m_int16_t)
    AMO_ENTITY_ARGS_SET(m_int32_t)
    AMO_ENTITY_ARGS_SET(m_int64_t)
    AMO_ENTITY_ARGS_SET(m_uint8_t)
    AMO_ENTITY_ARGS_SET(m_uint16_t)
    AMO_ENTITY_ARGS_SET(m_uint32_t)
    AMO_ENTITY_ARGS_SET(m_uin64_t)
    AMO_ENTITY_ARGS_SET(m_float)
    AMO_ENTITY_ARGS_SET(m_double)
    AMO_ENTITY_ARGS_SET(m_string)
    AMO_ENTITY_ARGS_SET_END()
    
    bool m_bool;
    char m_char;
    int8_t m_int8_t;
    int16_t m_int16_t;
    int32_t m_int32_t;
    int64_t m_int64_t;
    uint8_t m_uint8_t;
    uint16_t m_uint16_t;
    uint32_t m_uint32_t;
    uint64_t m_uin64_t;
    float m_float;;
    double m_double;
    std::string m_string;
    
};

class JsonInfoClass : public amo::json_object {
public:
    AMO_ENTITY_ARGS_GET_BEGIN(JsonInfoClass)
    AMO_ENTITY_ARGS_GET(m_jsoninfo1)
    AMO_ENTITY_ARGS_GET(m_jsoninfo2)
    AMO_ENTITY_ARGS_GET_END()
    
    AMO_ENTITY_ARGS_SET_BEGIN(JsonInfoClass)
    AMO_ENTITY_ARGS_SET(m_jsoninfo1)
    AMO_ENTITY_ARGS_SET(m_jsoninfo2)
    AMO_ENTITY_ARGS_SET_END()
    
    JsonInfoPod m_jsoninfo1;
    std::shared_ptr<JsonInfoPod> m_jsoninfo2;
};

namespace amo {
    template<> class is_container<std::vector<std::vector<int> > > {
    public:
        const static int32_t value = 1;
    };
    template<> class is_container< std::vector< std::list<  JsonInfoClass > > > {
    public:
        const static int32_t value = 1;
    };
    
}


class JsonInfoContainer : public amo::json_object {

public:
    AMO_ENTITY_ARGS_GET_BEGIN(JsonInfoContainer)
    AMO_ENTITY_ARGS_GET(vec1)
    AMO_ENTITY_ARGS_GET(vec2)
    AMO_ENTITY_ARGS_GET(vec3)
    AMO_ENTITY_ARGS_GET(m_jsoninfo2)
    AMO_ENTITY_ARGS_GET(m_jsoninfo3)
    AMO_ENTITY_ARGS_GET(m_jsoninfo4)
    AMO_ENTITY_ARGS_GET(m_jsoninfo5)
    AMO_ENTITY_ARGS_GET(m_jsoninfo6)
    AMO_ENTITY_ARGS_GET_END()
    
    AMO_ENTITY_ARGS_SET_BEGIN(JsonInfoContainer)
    AMO_ENTITY_ARGS_SET(vec1)
    AMO_ENTITY_ARGS_SET(vec2)
    AMO_ENTITY_ARGS_SET(vec3)
    AMO_ENTITY_ARGS_SET(m_jsoninfo2)
    AMO_ENTITY_ARGS_SET(m_jsoninfo3)
    AMO_ENTITY_ARGS_SET(m_jsoninfo4)
    AMO_ENTITY_ARGS_SET(m_jsoninfo5)
    AMO_ENTITY_ARGS_SET(m_jsoninfo6)
    AMO_ENTITY_ARGS_SET_END()
    
    std::deque< std::string > vec1;
    std::list< std::string > vec2;
    std::vector<std::vector<int> > vec3;
    std::shared_ptr<JsonInfoClass> m_jsoninfo2;
    JsonInfoClass m_jsoninfo3;
    std::vector< std::shared_ptr<JsonInfoClass> > m_jsoninfo4;
    std::vector<  JsonInfoClass > m_jsoninfo5;
    std::vector< std::list<  JsonInfoClass > > m_jsoninfo6;
};



class JsonInfoUnknown : public amo::json_object {
public:
    AMO_ENTITY_ARGS_GET_BEGIN(JsonInfoUnknown)
    AMO_ENTITY_ARGS_GET(m_bool)
    AMO_ENTITY_ARGS_GET(m_char)
    AMO_ENTITY_ARGS_GET(m_int8_t)
    AMO_ENTITY_ARGS_GET(m_int16_t)
    AMO_ENTITY_ARGS_GET(m_int32_t)
    AMO_ENTITY_ARGS_GET(m_int64_t)
    AMO_ENTITY_ARGS_GET(m_uint8_t)
    AMO_ENTITY_ARGS_GET(m_uint16_t)
    AMO_ENTITY_ARGS_GET(m_uint32_t)
    AMO_ENTITY_ARGS_GET(m_uin64_t)
    AMO_ENTITY_ARGS_GET(m_float)
    AMO_ENTITY_ARGS_GET(m_double)
    AMO_ENTITY_ARGS_GET(m_string)
    AMO_ENTITY_ARGS_GET(m_smart_int)
    AMO_ENTITY_ARGS_GET(vec1)
    AMO_ENTITY_ARGS_GET(vec2)
    AMO_ENTITY_ARGS_GET(vec3)
    AMO_ENTITY_ARGS_GET(m_jsoninfo2)
    AMO_ENTITY_ARGS_GET(m_jsoninfo3)
    AMO_ENTITY_ARGS_GET(m_jsoninfo4)
    AMO_ENTITY_ARGS_GET(m_jsoninfo5)
    AMO_ENTITY_ARGS_GET(m_json)
    AMO_ENTITY_ARGS_GET_END()
    
    AMO_ENTITY_ARGS_SET_BEGIN(JsonInfoUnknown)
    AMO_ENTITY_ARGS_SET(m_bool)
    AMO_ENTITY_ARGS_SET(m_char)
    AMO_ENTITY_ARGS_SET(m_int8_t)
    AMO_ENTITY_ARGS_SET(m_int16_t)
    AMO_ENTITY_ARGS_SET(m_int32_t)
    AMO_ENTITY_ARGS_SET(m_int64_t)
    AMO_ENTITY_ARGS_SET(m_uint8_t)
    AMO_ENTITY_ARGS_SET(m_uint16_t)
    AMO_ENTITY_ARGS_SET(m_uint32_t)
    AMO_ENTITY_ARGS_SET(m_uin64_t)
    AMO_ENTITY_ARGS_SET(m_float)
    AMO_ENTITY_ARGS_SET(m_double)
    AMO_ENTITY_ARGS_SET(m_string)
    AMO_ENTITY_ARGS_SET(m_smart_int)
    AMO_ENTITY_ARGS_SET(vec1)
    AMO_ENTITY_ARGS_SET(vec2)
    AMO_ENTITY_ARGS_SET(vec3)
    AMO_ENTITY_ARGS_SET(m_jsoninfo2)
    AMO_ENTITY_ARGS_SET(m_jsoninfo3)
    AMO_ENTITY_ARGS_SET(m_jsoninfo4)
    AMO_ENTITY_ARGS_SET(m_jsoninfo5)
    AMO_ENTITY_ARGS_SET(m_json)
    AMO_ENTITY_ARGS_SET_END()
    
    bool m_bool;
    char m_char;
    int8_t m_int8_t;
    int16_t m_int16_t;
    int32_t m_int32_t;
    int64_t m_int64_t;
    uint8_t m_uint8_t;
    uint16_t m_uint16_t;
    uint32_t m_uint32_t;
    uint64_t m_uin64_t;
    float m_float;;
    double m_double;
    std::string m_string;
    
    std::shared_ptr<int32_t> m_smart_int;
    std::vector<int> vec1;
    std::vector< std::shared_ptr<int> > vec2;
    std::vector<std::vector<int> > vec3;
    std::shared_ptr<JsonInfoPod> m_jsoninfo2;
    JsonInfoPod m_jsoninfo3;
    std::vector< std::shared_ptr<JsonInfoClass> > m_jsoninfo4;
    std::vector<  JsonInfoClass> m_jsoninfo5;
    
    amo::json m_json;
};



MAKE_TYPE_TRAITS(JsonInfoPod)
MAKE_TYPE_TRAITS(JsonInfoClass)



template<typename T, typename R>

class AAA {
    static T get_value_from_json2(
        const std::string& key,
        const T& default_val) {
        
        return default_val;
    }
};


template<typename T>

class AAA<T, int> {
public:
    static  T get_value_from_json2(
        const std::string& key,
        const T& default_val) {
        
        return default_val;
    }
};

TEST(json_object, ObjectPod) {

    amo::json json;
    json.put("m_int8_t", 23);
    json.put("m_smart_int", 22);
    json.put("m_smart_int", 22);
    
    json.put("m_bool", true);
    json.put("m_char", 'c');
    json.put("m_int8_t", 8);
    json.put("m_int16_t", 16);
    json.put("m_int32_t", 32);
    json.put("m_int64_t", 64);
    json.put("m_uint8_t", 8);
    json.put("m_uint16_t", 16);
    json.put("m_uint32_t", 32);
    json.put("m_uin64_t", 64);
    json.put("m_float", 1.0);
    json.put("m_double", 2.22);
    json.put("m_string", "string txt");
    
    auto ptr = JsonInfoPod::fromJson(json);
    ASSERT_EQ(ptr->m_bool, json.getBool("m_bool"));
    ASSERT_EQ(ptr->m_char, json.getChar("m_char"));
    ASSERT_EQ(ptr->m_int8_t, json.getInt8("m_int8_t"));
    ASSERT_EQ(ptr->m_int16_t, json.getInt16("m_int16_t"));
    ASSERT_EQ(ptr->m_int32_t, json.getInt32("m_int32_t"));
    ASSERT_EQ(ptr->m_int64_t, json.getInt64("m_int64_t"));
    ASSERT_EQ(ptr->m_uint8_t, json.getUint8("m_uint8_t"));
    ASSERT_EQ(ptr->m_uint16_t, json.getUint16("m_uint16_t"));
    ASSERT_EQ(ptr->m_uint32_t, json.getUint32("m_uint32_t"));
    ASSERT_EQ(ptr->m_uin64_t, json.getUint64("m_uin64_t"));
    ASSERT_EQ(ptr->m_float, json.getDouble("m_float"));
    ASSERT_EQ(ptr->m_double, json.getDouble("m_double"));
    ASSERT_EQ(ptr->m_string, json.getString("m_string"));
}


TEST(json_object, ObjectClass) {


    amo::json json;
    json.put("m_int8_t", 23);
    json.put("m_smart_int", 22);
    json.put("m_smart_int", 22);
    
    json.put("m_bool", true);
    json.put("m_char", 'c');
    json.put("m_int8_t", 8);
    json.put("m_int16_t", 16);
    json.put("m_int32_t", 32);
    json.put("m_int64_t", 64);
    json.put("m_uint8_t", 8);
    json.put("m_uint16_t", 16);
    json.put("m_uint32_t", 32);
    json.put("m_uin64_t", 64);
    json.put("m_float", 1.0);
    json.put("m_double", 2.22);
    json.put("m_string", "string txt");
    
    amo::json info;
    info.put("m_jsoninfo1", json);
    info.put("m_jsoninfo2", json);
    
    auto jsonInfoClass = JsonInfoClass::fromJson(info);
    {
        auto ptr = &jsonInfoClass->m_jsoninfo1;
        ASSERT_EQ(ptr->m_bool, json.getBool("m_bool"));
        ASSERT_EQ(ptr->m_char, json.getChar("m_char"));
        ASSERT_EQ(ptr->m_int8_t, json.getInt8("m_int8_t"));
        ASSERT_EQ(ptr->m_int16_t, json.getInt16("m_int16_t"));
        ASSERT_EQ(ptr->m_int32_t, json.getInt32("m_int32_t"));
        ASSERT_EQ(ptr->m_int64_t, json.getInt64("m_int64_t"));
        ASSERT_EQ(ptr->m_uint8_t, json.getUint8("m_uint8_t"));
        ASSERT_EQ(ptr->m_uint16_t, json.getUint16("m_uint16_t"));
        ASSERT_EQ(ptr->m_uint32_t, json.getUint32("m_uint32_t"));
        ASSERT_EQ(ptr->m_uin64_t, json.getUint64("m_uin64_t"));
        ASSERT_EQ(ptr->m_float, json.getDouble("m_float"));
        ASSERT_EQ(ptr->m_double, json.getDouble("m_double"));
        ASSERT_EQ(ptr->m_string, json.getString("m_string"));
    }
    {
        auto ptr = jsonInfoClass->m_jsoninfo2;
        ASSERT_EQ(ptr->m_bool, json.getBool("m_bool"));
        ASSERT_EQ(ptr->m_char, json.getChar("m_char"));
        ASSERT_EQ(ptr->m_int8_t, json.getInt8("m_int8_t"));
        ASSERT_EQ(ptr->m_int16_t, json.getInt16("m_int16_t"));
        ASSERT_EQ(ptr->m_int32_t, json.getInt32("m_int32_t"));
        ASSERT_EQ(ptr->m_int64_t, json.getInt64("m_int64_t"));
        ASSERT_EQ(ptr->m_uint8_t, json.getUint8("m_uint8_t"));
        ASSERT_EQ(ptr->m_uint16_t, json.getUint16("m_uint16_t"));
        ASSERT_EQ(ptr->m_uint32_t, json.getUint32("m_uint32_t"));
        ASSERT_EQ(ptr->m_uin64_t, json.getUint64("m_uin64_t"));
        ASSERT_EQ(ptr->m_float, json.getDouble("m_float"));
        ASSERT_EQ(ptr->m_double, json.getDouble("m_double"));
        ASSERT_EQ(ptr->m_string, json.getString("m_string"));
    }
    
}




TEST(json_object, ObjectContainer) {


    amo::json vec1(false);
    vec1.push_back("1");
    vec1.push_back("2");
    vec1.push_back("3");
    
    amo::json vec2(false);
    vec2.push_back("4");
    vec2.push_back("5");
    vec2.push_back("6");
    
    amo::json vec3(false);
    
    {
        amo::json arr3(false);
        arr3.push_back(7);
        arr3.push_back(8);
        arr3.push_back(9);
        
        
        vec3.push_back(arr3);
        vec3.push_back(arr3);
        vec3.push_back(arr3);
        vec3.push_back(arr3);
        vec3.push_back(arr3);
        vec3.push_back(arr3);
        vec3.push_back(arr3);
        
    }
    
    
    amo::json json;
    json.put("vec1", vec1);
    json.put("vec2", vec2);
    json.put("vec3", vec3);
    
    
    amo::json info;
    info.put("m_bool", true);
    info.put("m_char", 'c');
    info.put("m_int8_t", 8);
    info.put("m_int16_t", 16);
    info.put("m_int32_t", 32);
    info.put("m_int64_t", 64);
    info.put("m_uint8_t", 8);
    info.put("m_uint16_t", 16);
    info.put("m_uint32_t", 32);
    info.put("m_uin64_t", 64);
    info.put("m_float", 1.0);
    info.put("m_double", 2.22);
    info.put("m_string", "string txt");
    
    json.put("m_jsoninfo2", info);
    json.put("m_jsoninfo3", info);
    
    
    amo::json jsoninfoclass;
    jsoninfoclass.put("m_jsoninfo1", info);
    jsoninfoclass.put("m_jsoninfo2", info);
    
    json.put("m_jsoninfo2", jsoninfoclass);
    json.put("m_jsoninfo3", jsoninfoclass);
    
    amo::json arr(false);
    arr.push_back(jsoninfoclass);
    arr.push_back(jsoninfoclass);
    arr.push_back(jsoninfoclass);
    arr.push_back(jsoninfoclass);
    
    json.put("m_jsoninfo4", arr);
    json.put("m_jsoninfo5", arr);
    
    amo::json arr2(false);
    arr2.push_back(arr);
    arr2.push_back(arr);
    arr2.push_back(arr);
    json.put("m_jsoninfo6", arr2);
    
    
    
    
    
    
    auto jsonInfoContainer = JsonInfoContainer::fromJson(json);
    
    json = info;
    
    {
        auto ptr = &jsonInfoContainer->m_jsoninfo3.m_jsoninfo1;
        ASSERT_EQ(ptr->m_bool, json.getBool("m_bool"));
        ASSERT_EQ(ptr->m_char, json.getChar("m_char"));
        ASSERT_EQ(ptr->m_int8_t, json.getInt8("m_int8_t"));
        ASSERT_EQ(ptr->m_int16_t, json.getInt16("m_int16_t"));
        ASSERT_EQ(ptr->m_int32_t, json.getInt32("m_int32_t"));
        ASSERT_EQ(ptr->m_int64_t, json.getInt64("m_int64_t"));
        ASSERT_EQ(ptr->m_uint8_t, json.getUint8("m_uint8_t"));
        ASSERT_EQ(ptr->m_uint16_t, json.getUint16("m_uint16_t"));
        ASSERT_EQ(ptr->m_uint32_t, json.getUint32("m_uint32_t"));
        ASSERT_EQ(ptr->m_uin64_t, json.getUint64("m_uin64_t"));
        ASSERT_EQ(ptr->m_float, json.getDouble("m_float"));
        ASSERT_EQ(ptr->m_double, json.getDouble("m_double"));
        ASSERT_EQ(ptr->m_string, json.getString("m_string"));
    }
    {
        auto ptr = jsonInfoContainer->m_jsoninfo2->m_jsoninfo2;
        ASSERT_EQ(ptr->m_bool, json.getBool("m_bool"));
        ASSERT_EQ(ptr->m_char, json.getChar("m_char"));
        ASSERT_EQ(ptr->m_int8_t, json.getInt8("m_int8_t"));
        ASSERT_EQ(ptr->m_int16_t, json.getInt16("m_int16_t"));
        ASSERT_EQ(ptr->m_int32_t, json.getInt32("m_int32_t"));
        ASSERT_EQ(ptr->m_int64_t, json.getInt64("m_int64_t"));
        ASSERT_EQ(ptr->m_uint8_t, json.getUint8("m_uint8_t"));
        ASSERT_EQ(ptr->m_uint16_t, json.getUint16("m_uint16_t"));
        ASSERT_EQ(ptr->m_uint32_t, json.getUint32("m_uint32_t"));
        ASSERT_EQ(ptr->m_uin64_t, json.getUint64("m_uin64_t"));
        ASSERT_EQ(ptr->m_float, json.getDouble("m_float"));
        ASSERT_EQ(ptr->m_double, json.getDouble("m_double"));
        ASSERT_EQ(ptr->m_string, json.getString("m_string"));
    }
    
}


TEST(json_object, ObjectUnknown) {


    amo::json json;
    json.put("m_int8_t", 23);
    json.put("m_smart_int", 22);
    amo::json arr;
    arr.set_array();
    arr.push_back(1);
    arr.push_back(2);
    arr.push_back(3);
    amo::json arr2;
    arr2.set_array();
    arr2.push_back(arr);
    arr2.push_back(arr);
    arr2.push_back(arr);
    
    json.put("vec1", arr);
    json.put("vec2", arr);
    json.put("vec3", arr2);
    amo::json jsoninfo = json;
    amo::json jsoninfoarr(false);
    jsoninfoarr.set_array();
    jsoninfoarr.push_back(jsoninfo);
    jsoninfoarr.push_back(jsoninfo);
    jsoninfoarr.push_back(jsoninfo);
    json.put("m_jsoninfo2", jsoninfo);
    json.put("m_jsoninfo3", jsoninfo);
    json.put("m_jsoninfo4", jsoninfoarr);
    json.put("m_jsoninfo5", jsoninfoarr);
    std::cout << json.format() << std::endl;;
    
    int count = 0;
    amo::timer t;
    auto ptr = JsonInfoUnknown::fromJson(json);
    ptr->m_double = 11.3;
    ptr->vec1[0] = 88;
    *ptr->vec2[1] = 14;
    ptr->vec3[1][2] = 14;
    
    
    
    
    std::cout << count << std::endl;
    std::cout << "use time: " << t.elapsed() << std::endl;
    
    /*AAA<int, int>::get_value_from_json2(std::string("23423"), 4);
    std::cout << ptr->m_int8_t << std::endl;*/
    
}